﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Kokomo.Mirror.CliMetadata.Signatures
{
	public static class SignatureReader
	{
		#region Signatures
		public static Signature ReadSignature(SignatureKind kind, byte[] data)
		{
			if (data == null) throw new ArgumentNullException("data");
			if (data.Length == 0) throw new ArgumentException("Cannot read from empty array");

			BitReader reader = new BitReader(data);
			Signature signature;
			switch (kind)
			{
				case SignatureKind.MethodRefSig:
					signature = reader.ReadMethodRefSignature();
					break;
				case SignatureKind.MethodDefSig:
					signature = reader.ReadMethodDefSig();
					break;
				case SignatureKind.FieldSig:
					signature = reader.ReadFieldSignature();
					break;
				case SignatureKind.PropertySig:
					signature = reader.ReadPropertySig();
					break;
				case SignatureKind.LocalVarSig:
					signature = reader.ReadLocalVariablesSignature();
					break;
				case SignatureKind.TypeSpec:
					signature = reader.ReadTypeSpecSignature();
					break;
				case SignatureKind.MethodSpec:
					signature = reader.ReadMethodSpecSignature();
					break;
				case SignatureKind.MemberRefSig:
					if (data[0] == (byte)SignatureFlags.Field)
						signature = reader.ReadFieldSignature();
					else
						signature = reader.ReadMethodRefSignature();
					break;
				case SignatureKind.Unknown:
				default:
					throw new ArgumentException("Invalid signature kind");
					break;
			}

			return signature;
		}

		private static void ThrowMalformedSignature(this BitReader reader)
		{
			throw new MalformedSignatureException(reader.Data, reader.Position);
		}
		public static FieldSignature ReadFieldSignature(this BitReader reader)
		{
			SignatureFlags flags = reader.ReadFlags();
			if (flags != SignatureFlags.Field)
				reader.ThrowMalformedSignature();

			FieldSignature fieldSig = new FieldSignature();
			//fieldSig.customMods = this.ReadCustomMods();
			fieldSig.type = reader.ReadType();

			return fieldSig;
		}
		public static PropertySignature ReadPropertySig(this BitReader reader)
		{
			SignatureFlags flags = reader.ReadFlags();
			if (
				(flags != SignatureFlags.Property)
				&& (flags != (SignatureFlags.Property | SignatureFlags.HasThis))
				)
				reader.ThrowMalformedSignature();

			PropertySignature propertySig = new PropertySignature();

			int parameterCount = reader.ReadCompressedInt();
			//propertySig.customMods = this.ReadCustomMods();
			propertySig.type = reader.ReadType();
			propertySig.parameters = reader.ReadParams(parameterCount);

			return propertySig;
		}
		public static LocalVariableSignature ReadLocalVariablesSignature(byte[] data)
		{
			return (LocalVariableSignature)ReadSignature(SignatureKind.LocalVarSig, data);
		}
		public static LocalVariableSignature ReadLocalVariablesSignature(this BitReader reader)
		{
			SignatureFlags flags = reader.ReadFlags();
			if (flags != SignatureFlags.LocalSig)
				reader.ThrowMalformedSignature();

			LocalVariableSignature localVarSig = new LocalVariableSignature();
			int count = reader.ReadCompressedInt();

			VariableSignature[] variables = new VariableSignature[count];
			for (int i = 0; i < count; i++)
			{
				VariableSignature variable = new VariableSignature();

				SignatureFlags variableFlags = reader.PeekFlags();
				if (variableFlags == SignatureFlags.TypedByRef)
				{
					variable.isTypedByRef = true;
				}
				else
				{
					CustomModifiers mods = reader.ReadCustomMods();
					variableFlags = reader.PeekFlags();
					ConstraintFlags constraint = ConstraintFlags.None;
					if (IsConstraintToken(variableFlags))
					{
						constraint = reader.ReadConstraint();
						variable.constraints = constraint;
						variableFlags = reader.PeekFlags();
					}
					if (variableFlags == SignatureFlags.ByRef)
					{
						variable.isByRef = true;
						reader.ReadByte();
					}

					variable.type = reader.ReadType();
				}

				variables[i] = variable;
			}

			localVarSig.variables = variables;

			return localVarSig;
		}
		#endregion

		private static byte PeekByte(this BitReader reader)
		{
			int n = reader.ReadByte();
			byte b = (n >= 0) ? (byte)n : (byte)0;
			return b;
		}
		private static SignatureFlags ReadFlags(this BitReader reader)
		{
			return (SignatureFlags)reader.ReadByte();
		}
		private static SignatureFlags PeekFlags(this BitReader reader)
		{
			return (SignatureFlags)reader.PeekByte();
		}


		private static CustomModifier ReadCustomMod(this BitReader reader)
		{
			SignatureFlags flags = reader.ReadFlags();
			int typeToken = reader.ReadTypeDefOrRef();

			return new CustomModifier(
				(flags == SignatureFlags.CustomModifierOptional),
				typeToken
				);
		}

		private static ConstraintFlags ReadConstraint(this BitReader reader)
		{
			SignatureFlags flags = reader.ReadFlags();
			if (flags == SignatureFlags.Pinned) return ConstraintFlags.Pinned;
			else reader.ThrowMalformedSignature();

			return ConstraintFlags.None;
		}

		private static bool IsConstraintToken(SignatureFlags flags)
		{
			bool isConstraint =
				(flags == SignatureFlags.Pinned)
				;

			return isConstraint;
		}
		private static bool IsCustomModToken(SignatureFlags flags)
		{
			bool isCustomModToken =
				(flags == SignatureFlags.CustomModifierOptional)
				|| (flags == SignatureFlags.CustomModifierRequired)
				;

			return isCustomModToken;
		}
		private static CustomModifiers ReadCustomMods(this BitReader reader)
		{
			SignatureFlags flags = reader.PeekFlags();
			bool hasMods = IsCustomModToken(flags);

			if (!hasMods) return null;

			List<int> requiredMods = new List<int>();
			List<int> optionalMods = new List<int>();

			while (IsCustomModToken(flags))
			{
				CustomModifier customMod = reader.ReadCustomMod();
				if (customMod.isOptional)
					optionalMods.Add(customMod.typeToken);
				else
					requiredMods.Add(customMod.typeToken);

				//customMods.Add(customMod);

				flags = reader.PeekFlags();
			}

			if (!hasMods)
				return null;

			return new CustomModifiers()
			{
				RequiredModifierTypeTokens = requiredMods.ToArray(),
				OptionalModifierTypeTokens = requiredMods.ToArray()
			};
			//return customMods.ToArray();
		}
		private static int ReadTypeDefOrRef(this BitReader reader)
		{
			int codedIndexValue = reader.ReadCompressedInt();
			CodedIndex codedIndex = CodedIndex.Parse(codedIndexValue, CodedIndexType.TypeDefOrRef);
			return codedIndex.Token;
		}
		private static Param ReadParam(this BitReader reader)
		{
			Param param = new Param();
			reader.ReadParam(param);
			return param;
		}
		private static Param[] ReadParams(this BitReader reader, int paramCount)
		{
			Param[] parameters = new Param[paramCount];
			for (int i = 0; i < paramCount; i++)
			{
				Param parameter = reader.ReadParam();
				parameters[i] = parameter;
			}

			return parameters;
		}
		private static void ReadParam(this BitReader reader, Param param)
		{
			param.customModifiers = reader.ReadCustomMods();

			SignatureFlags flags = reader.PeekFlags();
			if (flags == SignatureFlags.TypedByRef)
			{
				reader.ReadByte();
				param.isTypedByRef = true;
			}
			else
			{
				if (flags == SignatureFlags.ByRef)
				{
					reader.ReadByte();
					param.isByRef = true;
				}
				param.type = reader.ReadType();
			}
		}
		private static ReturnType ReadRetType(this BitReader reader)
		{
			ReturnType param = new ReturnType();
			reader.ReadParam(param);
			return param;
		}

		internal static TypeSpec ReadType(this BitReader reader)
		{
			// HACK: This isn't in the Partition II spec
			//CustomModifier[] customMods = this.ReadCustomMods();
			CustomModifiers customMods = reader.ReadCustomMods();

			SignatureElementType elementType = (SignatureElementType)reader.ReadByte();

			TypeSpec typeSpec;
			switch (elementType)
			{
				case SignatureElementType.Boolean:
				case SignatureElementType.Char:
				case SignatureElementType.SByte:
				case SignatureElementType.Byte:
				case SignatureElementType.Int16:
				case SignatureElementType.UInt16:
				case SignatureElementType.Int32:
				case SignatureElementType.UInt32:
				case SignatureElementType.Int64:
				case SignatureElementType.UInt64:
				case SignatureElementType.SingleFloat:
				case SignatureElementType.DoubleFloat:
				case SignatureElementType.IntPtr:
				case SignatureElementType.UIntPtr:
				case SignatureElementType.Object:
				case SignatureElementType.String:
				case SignatureElementType.Void:
					typeSpec = PrimitiveTypeSpec.Get(elementType);
					break;
				case SignatureElementType.Array:
					ArrayTypeSpec array = new ArrayTypeSpec();
					array.arrayElementType = reader.ReadType();
					array.arrayShape = reader.ReadArrayShape();
					typeSpec = array;
					break;
				case SignatureElementType.Class:
				case SignatureElementType.ValueType:
					typeSpec = new TypeRef() { typeToken = reader.ReadTypeDefOrRef() };
					break;
				case SignatureElementType.FunctionPointer:
					typeSpec = new FunctionPointerTypeSpec() { methodRefSig = reader.ReadMethodDefSig() };
					break;
				case SignatureElementType.GenericInst:
					{
						GenericTypeSpec genericTypeSpec = new GenericTypeSpec();
						byte token = reader.ReadByte();	// CLASS or VALUETYPE
						genericTypeSpec.typeToken = reader.ReadTypeDefOrRef();
						int genericArgCount = reader.ReadCompressedInt();
						TypeSpec[] genericArgs = new TypeSpec[genericArgCount];
						for (int i = 0; i < genericArgCount; i++)
						{
							TypeSpec typeArg = reader.ReadType();
							genericArgs[i] = typeArg;
						}
						genericTypeSpec.genericArguments = genericArgs;
						typeSpec = genericTypeSpec;
					}
					break;
				case SignatureElementType.GenericTypeParameter:
					typeSpec = new GenericTypeParamTypeSpec() { index = reader.ReadCompressedInt() };
					break;
				case SignatureElementType.GenericMethodParameter:
					typeSpec = new GenericMethodParamTypeSpec() { index = reader.ReadCompressedInt() };
					break;
				case SignatureElementType.UnmanagedPointer:
					{
						PointerTypeSpec pointerType = new PointerTypeSpec();
						//pointerType.customModifiers = this.ReadCustomMods();
						//byte token = this.PeekByte();
						pointerType.type = reader.ReadType();
						//if (token != (byte)SignatureElementType.Void)
						//{
						//    pointerType.type = this.ReadType();
						//}
						//else
						//{
						//    this.ReadByte();
						//}
						typeSpec = pointerType;
					}
					break;
				case SignatureElementType.SzArray:
					VectorTypeSpec vectorType = new VectorTypeSpec();
					//vectorType.customModifiers = this.ReadCustomMods();
					vectorType.type = reader.ReadType();
					typeSpec = vectorType;
					break;
				default:
					throw new ArgumentException("Unknown type spec");
			}

			typeSpec.customMods = customMods;
			return typeSpec;
		}

		private static int[] ReadCompressedInts(this BitReader reader, int count)
		{
			int[] nums = new int[count];
			for (int i = 0; i < count; i++)
			{
				nums[i] = reader.ReadCompressedInt();
			}
			return nums;
		}
		private static ArrayShape ReadArrayShape(this BitReader reader)
		{
			ArrayShape shape = new ArrayShape();
			shape.rank = reader.ReadCompressedInt();
			int numSizes = reader.ReadCompressedInt();
			shape.sizes = reader.ReadCompressedInts(numSizes);
			int numLowerBounds = reader.ReadCompressedInt();
			shape.lowerBounds = reader.ReadCompressedInts(numLowerBounds);

			return shape;
		}
		private static MethodRefSignature ReadMethodDefSig(this BitReader reader)
		{
			SignatureFlags flags = reader.ReadFlags();
			MethodRefSignature methodDefSig = new MethodRefSignature();
			methodDefSig.flags = flags;
			if (flags.HasFlag(SignatureFlags.Generic))
				methodDefSig.genericParameterCount = reader.ReadCompressedInt();

			int paramCount = reader.ReadCompressedInt();
			methodDefSig.returnType = reader.ReadRetType();
			methodDefSig.parameters = reader.ReadParams(paramCount);

			return methodDefSig;
		}
		public static MethodRefSignature ReadMethodRefSignature(byte[] data)
		{
			return (MethodRefSignature)ReadSignature(SignatureKind.MethodRefSig, data);
		}
		private static MethodRefSignature ReadMethodRefSignature(this BitReader reader)
		{
			return reader.ReadMethodDefSig();
		}
		private static TypeSpecSignature ReadTypeSpecSignature(this BitReader reader)
		{
			TypeSpec typeSpec = reader.ReadType();
			TypeSpecSignature signature = new TypeSpecSignature(typeSpec);
			return signature;
		}
		private static MethodSpec ReadMethodSpecSignature(this BitReader reader)
		{
			SignatureFlags flags = reader.ReadFlags();
			if (flags != SignatureFlags.GenericInstantiation)
				reader.ThrowMalformedSignature();

			int genericArgCount = reader.ReadCompressedInt();
			TypeSpec[] genericArgs = new TypeSpec[genericArgCount];
			for (int i = 0; i < genericArgCount; i++)
			{
				TypeSpec typeArg = reader.ReadType();
				genericArgs[i] = typeArg;
			}

			MethodSpec methodSpec = new MethodSpec() { genericArguments = genericArgs };
			return methodSpec;
		}
	}
}
